package com.example.spider.proxy.core;

import cn.hutool.core.util.StrUtil;
import com.example.spider.proxy.core.tester.ProxyTypeEnum;
import com.fasterxml.jackson.annotation.JsonIgnore;
import jakarta.annotation.Nonnull;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.experimental.Accessors;

import java.net.InetSocketAddress;
import java.net.Proxy;
import java.time.LocalDateTime;
import java.util.Map;
import java.util.Objects;

/**
 * 代理信息
 */
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@Accessors(chain = true)
//@JsonIgnoreProperties(ignoreUnknown = true)//版本兼容
public class ProxyInfo implements Comparable<ProxyInfo> {

    /**
     * 代理服务器 IP
     */
    private String ip;

    /**
     * 代理服务器 PORT
     */
    private int port;

    //private String test;
    /**
     * 标签信息：匿名度
     * 普通代理：显示源ip、代理服务器类型、Via标记
     * 匿名代理：隐藏源ip、请求头有代理服务器类型、Via xxProxy 标记
     * 混淆：via 可能随机的
     * 高匿代理：隐藏源ip、请求头没有代理服务器标记，同普通请求
     * transparent(透明)、anonymous(匿名)、distorting(混淆)、elite(高匿)；
     */
    private String anonymity;

    /**
     * 类型 HTTP / SOCKS
     * http、https、socks4、socks5和socks
     * HTTP
     * 1. HTTP 试一次 http://httpbin.org/get?show_env=1 / https://httpbin.org/get?show_env=1
     * 2. HTTPS 试一次 https://baidu.com 能通就行
     * 3. SOCKS5 试一次 https://baidu.com 能通就行
     * 4. SOCKS4 试一次 https://baidu.com 能通就行（SOCK4不支持UDP，不支持IPv6，不支持用户名密码认证，不支持防火墙穿透）
     */
    private ProxyTypeEnum proxyTypeEnum;

    /**
     * @deprecated
     */
    private Proxy.Type type;

    /**
     * 标签信息：从哪里获取的
     */
    private String source;

    /**
     * 是否支持 HTTPS
     *
     * @deprecated
     */
    private Boolean supportHttps;

    /**
     * 账号
     */
    private String username;

    /**
     * 密码
     */
    private String password;

    /**
     * 标签信息：代理服务器位置
     */
    private String location;

    /**
     * 标签信息：运营商
     */
    private String operator;

    /**
     * 响应速度 ms
     * 粗略计算，本次请求和上次请求平均，越近的请求影响越大，总成功会趋近于平均耗时，总失败会趋近于超时时间
     */
    private long responseTime;

    /**
     * 标签信息：创建时间
     */
    private LocalDateTime createTime;

    /**
     * 标签信息：最后验证时间
     */
    private LocalDateTime lastVerificationTime;

    /**
     * 扩展属性
     */
    private Map<String, String> extendInfo;

    /**
     * 内存里才有用
     */
    @JsonIgnore
    private transient Proxy proxy;

    /**
     * 请求成功数目
     */
    private long successCount;

    /**
     * 请求失败数目
     */
    private long failCount;

    /**
     * 总请求数目
     */
    private long totalCount;

    /**
     * 成功百分比 [0,100]
     */
    private volatile int successPercentage;

    /**
     * 连续成功数目
     */
    private volatile int successContinuityCount;

    /**
     * 连续失败数目
     */
    private int failContinuityCount;

    /**
     * 得分
     */
    private volatile int score;

    /**
     * 如果需要账号密码，但是没有，则为true，表示无效，不必测试
     */
    private boolean invalid;

    /**
     * 暂时不校验
     */
    //private LocalDateTime nextValidateTime;
    public static Proxy.Type resolveType(String type) {
        boolean socket = StrUtil.containsIgnoreCase(type, "sock");
        return socket ? Proxy.Type.SOCKS : Proxy.Type.HTTP;
    }

    public synchronized void success(long costTime) {
        lastVerificationTime = LocalDateTime.now();
        responseTime = responseTime <= 0 ? costTime : (costTime + responseTime) / 2;
        successCount++;
        totalCount++;
        successPercentage = (int) (successCount * 100 / totalCount);
        successContinuityCount++;
        failContinuityCount = 0;
        score = calculateScore();
    }

    public synchronized void fail(long costTime) {
        costTime = costTime > 5_000 ? 5_000 : costTime;
        lastVerificationTime = LocalDateTime.now();
        responseTime = responseTime <= 0 ? costTime : (costTime + responseTime) / 2;
        failCount++;
        totalCount++;
        successPercentage = (int) (successCount * 100 / totalCount);
        successContinuityCount = 0;
        failContinuityCount++;
        score = calculateScore();
    }

    /**
     * 本次请求时间得分，成功才有
     * 0ms 100
     * 50ms 97
     * 500ms 70
     * 5s
     *
     * @return [0, 20]
     */
    private int calculateBasicScore(long costTime) {
        if (costTime < 500) {
            // 500ms 以内都比较快 (评分 30 -> 20)
            return 30 - (int) costTime / 50;
        } else if (costTime < 2000) {
            // 500ms-2000ms 算快的 (评分 20 -> 10)
            return 20 - (int) costTime / 200;
        } else {
            // 2000ms+ 一般 (评分 10 -> 0)
            int r = 10 - (int) costTime / 400;
            return Math.max(r, 0);
        }
    }

    /**
     * 成功率得分
     */
    private int calculateSuccessPercentageScore() {
        if (successPercentage > 85) {
            // > 85% = stable = 45 - 50
            return 50 - (100 - successPercentage) / 3;
        } else if (successPercentage > 50) {
            // > 50% = 30 - 45
            return 45 - (85 - successPercentage) / 2;
        } else if (successPercentage > 20) {
            // > 20% = 0 - 30
            return 30 - (50 - successPercentage);
        } else if (successPercentage > 10) {
            return 0;
        } else {
            return -2 * successPercentage;
        }
    }

    /**
     * 计算得分
     * 因素：最近响应时间、整体成功率、连续成功/失败数目
     * 成功才有响应时间奖励分。失败不加
     *
     * @return [-50, +∞)
     */
    private int calculateScore() {
        if (invalid) {
            return -50;
        }
        // 响应速度得分[0, 30]
        int total = 0;
        int speedScore = calculateBasicScore(responseTime);
        if (totalCount > 10 && successPercentage < 40) {
            // 如果成功率低，则不过多参考响应时间得分
            speedScore = (int) (successPercentage / 60.0 * speedScore);
        }
        total += speedScore;

        // 历史整体成功率表现 [-20, 50]
        int successPercentageScore = calculateSuccessPercentageScore();

        // 最近请求表现，影响下次请求成功率

        // [-10, 0]最近连续失败次数越高，则追加扣除分数
        int continuityFailScore = -Math.min(Math.min(successContinuityCount, 3) / 2, 10);

        // [3,10]最近连续成功次数越高，则追加奖励分数
        int continuitySuccessScore = Math.min(Math.max(successContinuityCount, 3), 10);

        if (totalCount < 5) {
            // 请求次数小于5，历史成功率比重不具有参考价值，使用标准0分
            return (int) (successCount * 2);
        } else if (totalCount < 20) {
            // 5 分制
            total += successPercentage / 20;
            total += successPercentageScore;
            total += continuityFailScore;
            total += continuitySuccessScore;
        } else {
            // 10 分制
            total += successPercentage / 10;
            total += successPercentageScore;
            total += continuityFailScore;
            total += continuitySuccessScore;
        }

        return total;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) {
            return true;
        }
        if (o == null || getClass() != o.getClass()) {
            return false;
        }
        ProxyInfo proxyInfo = (ProxyInfo) o;
        return port == proxyInfo.port && ip.equals(proxyInfo.ip);
    }

    @Override
    public int hashCode() {
        return Objects.hash(ip, port);
    }

    public static long parseSecondToMill(String second) {
        return Double.valueOf(second).longValue() * 1000;
    }

    /**
     * 是否可用
     */
    //    private volatile boolean available;
    //
    //    private long lastSuccessfulTime;//最近一次请求成功时间
    //
    //    private long successfulTotalTime;//请求成功总耗时
    //
    //    private double successfulAverageTime;//成功请求平均耗时
    public synchronized Proxy toProxy() {
        if (proxyTypeEnum == null) {
            // 兼容代码
            proxyTypeEnum = ProxyTypeEnum.of(getType().name());
        }
        return Objects.requireNonNullElseGet(proxy, () -> proxy = new Proxy(proxyTypeEnum.getType(), new InetSocketAddress(ip, port)));
    }

    @Override
    public int compareTo(@Nonnull ProxyInfo o) {
        return Integer.compare(getScore(), o.getScore());
    }

    /**
     * 清空历史表现
     */
    public void cleanHistory() {
        lastVerificationTime = null;
        responseTime = 0;
        successCount = 0;
        failCount = 0;
        totalCount = 0;
        successPercentage = 0;
        successContinuityCount = 0;
        failContinuityCount = 0;
        score = 0;
    }
}
